import Sort from "./sort";
import type { Options, Column, Record, Rule, FieldPath} from "./type"
import { awaitTime, validatorDecimal } from "./util";

export default class Validate extends Sort {
    constructor(options: Options){
        super(options)
    }

    isCellError(row: Record, column: Column){
        const rowKey = row[this.props.rowKey]
        const errorKey = Array.isArray(column.dataIndex) ? column.dataIndex.join('-') : column.dataIndex;
        return this.errorState[rowKey] && this.errorState[rowKey][errorKey]
    }

    validate(){
        this.clearValidate();
        return this.validateFields()
    }
    async validateFields(filedList?: FieldPath[], rowindex?: number){
        const rules: Rule[] = this.getRules(filedList);
        if (!rules.length) return;
        await awaitTime(0);

        const fns = [];
        for(const rule of rules){
            // 验证所有行
            if (rowindex === undefined) fns.push(...await this.validateRows(rule));
            // 验证指定行
            else if(rowindex !== null) fns.push(this.validateRow(rule, this.dataSource.value[rowindex], rowindex))
        }

        const results = await Promise.allSettled(fns);
        const errors = results.filter(item => item.status === 'rejected');

        if (errors.length) {
            if (this.props.customEditer?.scrollToFirstError) {
                this.scrollToField();
            }
            
            const ethrowInfo: any[] = []
            errors.forEach((item: { status: 'rejected', reason: any }) => {
                ethrowInfo.push(item.reason)
            });
            return Promise.reject(ethrowInfo) ;
        }
    }

    clearValidate(filedList?: FieldPath[], rowindex?: number) {
        if (filedList ) {
            const removeError = (rows: Record[], filedList: FieldPath[]) => {
                rows.forEach((row: Record) => {
                    const rowKey = row[this.props.rowKey]
                    filedList.forEach((field: FieldPath) => {
                        const errorKey = Array.isArray(field) ? field.join('-') : field;
                        if (this.errorState[rowKey] && this.errorState[rowKey][errorKey]) {
                            delete this.errorState[rowKey][errorKey]
                        }
                    })
                })
            }

            if (rowindex !== undefined) {
                removeError([this.dataSource.value[rowindex]], filedList)
            } else {
                removeError(this.dataSource.value, filedList)
            }

            return;
        }

        Object.entries(this.errorState).forEach(([key]) => {
            delete this.errorState[key]
        })
    }

    scrollToField() {
        const errorCell = this.table.value?.querySelector('td.error');
        errorCell?.scrollIntoView?.({
            behavior: "smooth",
            block: "center",
            inline: "nearest"
        })
    }

    // 验证单个单元格
    async tryValidateCell(rowindex: number, colindex: number, trigger: 'blur'| 'change' | 'selected' | 'submit' = 'submit'){

        if ([undefined, -1, null, ''].includes(rowindex) || [undefined, -1, null, ''].includes(colindex)) return;

        if (this.props.keepLastRowEmpty && rowindex === this.dataSource.value.length - 1) return;

        // console.log(trigger)
        const row = this.dataSource.value[rowindex]
        const dataIndex = this.columns.value[colindex].dataIndex;
        const allRules = this.getRules();
        const currentRules = allRules.filter(rule => {
            const columnErrorKey = Array.isArray(dataIndex) ? dataIndex.join('-') : dataIndex;
            const errorKey = this.getErrorKey(rule);
            return columnErrorKey === errorKey;
        })

        // 内容变动时，自动移除错误记录
        // console.log(trigger)
        if (trigger === 'change'){
            currentRules.forEach(rule => {
                this.removeRecordError(rule, row)
            });
        }

        await awaitTime(0);
        const rules = currentRules?.filter(rule => {
            return rule.trigger === trigger;
        })
        
        rules?.forEach((rule: Rule) => {
            this.validateRow(rule, row, rowindex)?.then?.(() => {
                this.removeRecordError(rule, row)
            })?.catch?.((e) => {
                this.recordError(rule, row, e)
            })
        })
    }

    private getRules(filedList?: FieldPath[]) :Rule[] {
        
        const columnRules: Rule[] = [];
        const pushRule = (rules?: Rule[] | Rule, extendField?: Record) => {
            if (Array.isArray(rules)) columnRules.push(
                ...rules.map(v => ({
                    trigger: 'submit',
                    ...extendField,
                    ...v,
                }))
            );
            else if (rules) columnRules.push({ trigger: 'submit', ...extendField, ...rules });
        }

        let rules: Rule | Rule[] = this.props.customEditer?.rules ?? [];
        pushRule(rules);

        this.columns.value.forEach((column: Column) => {
            pushRule(column.rules, { field: column.dataIndex })
        })

        const filterRule = (rules: Rule[]): Rule[]  => {
            if(!filedList?.length) return rules;
            return rules.filter((rule: Rule) => {
                return filedList.find(field => {
                    // 同数组类型
                    if(Array.isArray(field) && Array.isArray(rule.field)) return field.join('-') === rule.field.join('-');
                    return field === rule.field
                })
            }) ?? [];
        }

        return filterRule(columnRules);
    }
    private async validateRows(rule: Rule): Promise<Promise<unknown>[]>{
        const fns = []
        const rows = this.dataSource.value?.slice?.(0, this.props.keepLastRowEmpty ? this.dataSource.value.length - 1 : undefined) ?? [];
        let rowindex = 0;
        for(const row of rows){
            const fn = this.validateRow(rule, row, rowindex)

            fns.push(fn)
            rowindex += 1;
        }
        return fns
    }
    private async validateRow(rule: Rule, row: Record, rowindex: number): Promise<any> {
        const { field, len, min, max, message, pattern, required, validator, precision } = rule;

        const value = this.getValueByModel(field, row);

        try{
            if (typeof validator === 'function') return await validator({
                rule, 
                value, 
                record: row, 
                rowindex
            }, (
                filedList?: FieldPath[], 
                rowindex?: number
            ) => {
                return this.validateFields(filedList, rowindex);
            })

            if (required && [undefined, null, ''].includes(value)) throw message;
    
            if (pattern instanceof RegExp && !pattern.test(value)) throw message;
    
            if (len && String(value).length > len) throw message;
    
            if (min !== undefined && Number(value) < min) throw message;
    
            if (max !== undefined && Number(value) > max) throw message;
    
            if (precision !== undefined) {
                await validatorDecimal(value, { maxDecimal: precision, message: message })
            }
        } catch (message) {
            const info = {
                message,
                value,
                rowindex,
                rule,
                record: row
            }
            this.recordError(rule, row, info)
            throw info;
        }
    }
    private getErrorKey(rule: Rule): string{
        const { field } = rule;
        const errorKey = Array.isArray(field) ? field.join('-') : field;
        return errorKey;
    }
    private recordError(rule: Rule, row: Record, error: any){
        const rowKey = row[this.props.rowKey]
        const errorKey = this.getErrorKey(rule);
        if (!this.errorState[rowKey]) {
            this.errorState[rowKey] = {}
        }
        this.errorState[rowKey][errorKey] = error || 'error.';
    }
    private removeRecordError(rule: Rule, row: Record){
        const rowKey = row[this.props.rowKey]
        const errorKey = this.getErrorKey(rule);
        if (!this.errorState[rowKey]) return;
        delete this.errorState[rowKey][errorKey];
    }
    
}